﻿// (c) Copyright Michael Antonio.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Net;
using System.Runtime.Serialization;
using System.Windows;
using MichMan.Utilities.Requests;

namespace MichMan.Utilities
{
    public static partial class FileManagerExtensions
    {
        public static List<ManagedFileInfo> DownloadedFiles(this FileManager fm)
        {
            return fm.GetFilesWithMetadata<DownloadState>();
        }

        public static DownloadState DownloadState(this ManagedFileInfo mfi)
        {
            return mfi.Metadata.Get<DownloadState>();
        }

        public static void DownloadState(this ManagedFileInfo mfi, DownloadState dfi)
        {
            mfi.Metadata.Put<DownloadState>(dfi);
        }
    }

    /// <summary>
    /// AppService that manages downloading files locally.
    /// </summary>
    public class DownloadManager : IApplicationService
    {
        public static DownloadManager Instance
        {
            get
            {
                return (DownloadManager) Services().FirstOrDefault(o => o.GetType() == typeof(DownloadManager));
            }
        }

        private static FileManager FileManager
        {
            get
            {
                return FileManager.Instance;
            }
        }

        private static IEnumerable<IApplicationService> Services()
        {
            foreach (var o in Application.Current.ApplicationLifetimeObjects)
            {
                yield return (IApplicationService)o;
            }
        }

        public readonly string DownloadManagerSettingsFile = "downloadmanager.xml";
        void IApplicationService.StartService(ApplicationServiceContext context)
        {
            /*
            if (DownloadManagerInfo.ResumeDownloadOnStartup)
            {
                StartNext();
            }
             */
        }

        private void StartNext()
        {
            ChangeCurrentRequest(GetNext());
        }


        private ManagedFileInfo GetNext()
        {
            FileManager fm = FileManager.Instance;
            List<ManagedFileInfo> files = fm.DownloadedFiles();

            if (files.Count == 0)
            {
                return null;
            }

            ManagedFileInfo mfi = files.OrderBy(fi => fi.DownloadState().Priority).FirstOrDefault(fi => fi.DownloadState().BytesReceived != fi.DownloadState().TotalBytesToReceive);
            Debug.WriteLine(String.Format("Downloading file Url: {0}", mfi == null ? null : mfi.Uri));
            return mfi;
        }

        private void ChangeCurrentRequest(ManagedFileInfo mfi)
        {
            CancelRequest(CurrentRequest);

            if (mfi == null)
            {
                CurrentFileInfo = null;
                CurrentRequest = null;
                return;
            }

            CurrentFileInfo = mfi;
            CurrentRequest = new DownloadIsoFileRequest(new Uri(mfi.Uri), mfi.Path, mfi.DownloadState().BytesReceived, mfi.DownloadState().TotalBytesToReceive);
            CurrentRequest.RunRequestProgressChanged += new RunRequestProgressChangedEventHandler(CurrentRequest_RunRequestProgressChanged);
            CurrentRequest.RunRequestCompleted += new RunRequestCompletedEventHandler(CurrentRequest_RunRequestCompleted);

            CurrentRequest.RunRequestAsync(mfi.DownloadState().UserState);
        }

        void CurrentRequest_RunRequestProgressChanged(object sender, RunRequestProgressChangedEventArgs e)
        {
            ManagedFileInfo mfi = this.FindEntry(e.UserState);
            if (mfi != null)
            {
                Debug.WriteLine(String.Format("Downloaded {0} of {1} for  file Url: {2}", e.BytesReceived, e.TotalBytesToReceive, mfi == null ? null : mfi.Uri));
                foreach (var handler in mfi.DownloadState().ProgressCallbacks)
                {
                    handler(sender, e);
                }
                mfi.DownloadState().BytesReceived = e.BytesReceived;
                mfi.DownloadState().TotalBytesToReceive = e.TotalBytesToReceive;
            }
        }

        void CurrentRequest_RunRequestCompleted(object sender, RunRequestCompletedEventArgs e)
        {
            ManagedFileInfo mfi = this.FindEntry(e.UserState);
            if (mfi != null)
            {
                foreach (var handler in mfi.DownloadState().Callbacks)
                {
                    handler(sender, e);
                }

                mfi.IsComplete = mfi.DownloadState().IsComplete;
            }
            StartNext();
        }

        void CancelRequest(DownloadIsoFileRequest request)
        {
            if (request != null)
            {
                if (request.IsBusy)
                {
                    request.CancelAsync();
                }
                ManagedFileInfo mfi = FindEntry(request.Uri, request.IsoFilePath);
                if (mfi != null)
                {
                    mfi.DownloadState().TotalBytesToReceive = request.TotalBytesToReceive;
                    mfi.DownloadState().BytesReceived = request.BytesReceived;
                }
            }
        }

        void IApplicationService.StopService() 
        {
            if (CurrentRequest != null)
            {
                CancelRequest(CurrentRequest);
            }
        }

        private ManagedFileInfo FindEntry(object userState)
        {
            return FileManager.DownloadedFiles().FirstOrDefault(dfi => Object.ReferenceEquals(dfi.DownloadState().UserState, userState));
        }

        internal ManagedFileInfo FindEntry(string uri)
        {
            return FileManager.DownloadedFiles().FirstOrDefault(dfi => dfi.Uri == uri);
        }

        private ManagedFileInfo FindEntry(Uri uri, string path)
        {
            return FileManager.DownloadedFiles().FirstOrDefault(dfi => dfi.Uri == uri.ToString() && dfi.Path == path);
        }


        public bool AddCallbacks(object userState, RunRequestProgressChangedEventHandler progressCallback, RunRequestCompletedEventHandler callback)
        {
            ManagedFileInfo mfi = FindEntry(userState);
            if (mfi != null)
            {
                if (progressCallback != null)
                {
                    mfi.DownloadState().ProgressCallbacks.Add(progressCallback);
                }
                if (callback != null)
                {
                    mfi.DownloadState().Callbacks.Add(callback);
                }
                return true;
            }

            return false;
        }

        public void RequestFile(Uri uri, string isoFilePath, int priority, RunRequestProgressChangedEventHandler progressCallback, RunRequestCompletedEventHandler callback, object userState)
        {
            int currentPriority = CurrentFileInfo != null ? CurrentFileInfo.DownloadState().Priority : int.MaxValue;

            ManagedFileInfo  mfi = FindEntry(uri, isoFilePath);
            if (mfi == null)
            {
                mfi = new ManagedFileInfo()
                {
                    Uri = uri.ToString(),
                    Path = isoFilePath,
                    IsComplete = false,
                };
                mfi.DownloadState(new DownloadState()
                    {
                        Priority = Math.Min(priority, 0),
                        TotalBytesToReceive = 0,
                        BytesReceived = 0,
                        UserState = userState
                    });
            }
            else
            {
                mfi.DownloadState().UserState = userState;
            }

            if (callback != null)
            {
                mfi.DownloadState().Callbacks.Add(callback);
            }

            if (progressCallback != null)
            {
                mfi.DownloadState().ProgressCallbacks.Add(progressCallback);
            }

            FileManager.Add(mfi);

            if (mfi.DownloadState().Priority < currentPriority)
            {
                ChangeCurrentRequest(mfi);
            }
            else if (priority == -1)
            {
                ChangeCurrentRequest(mfi);
            }
        }

        public DownloadManagerInfo DownloadManagerInfo
        {
            get
            {
                return Settings<DownloadManagerInfo>.Instance;
            }
        }

        public DownloadIsoFileRequest CurrentRequest { get; private set; }
        public ManagedFileInfo CurrentFileInfo { get; private set; }
    }

    [DataContract]
    public class DownloadManagerInfo
    {
        public DownloadManagerInfo()
        {
            ResumeDownloadOnStartup = true;
        }

        [DataMember]
        public bool ResumeDownloadOnStartup { get; set; }
    }

    [DataContract]
    public class DownloadState
    {
        public DownloadState()
        {
            Callbacks = new List<RunRequestCompletedEventHandler>();
            ProgressCallbacks = new List<RunRequestProgressChangedEventHandler>();
            Priority = int.MaxValue;
            Active = true;
        }

        [IgnoreDataMember]
        public bool IsComplete { get { return TotalBytesToReceive == BytesReceived; } }

        [DataMember]
        public long BytesReceived { get; set; }
        [DataMember]
        public long TotalBytesToReceive { get; set; }
        [DataMember]
        public int Priority { get; set; }
        [DataMember]
        public bool Active { get; set; }

        [IgnoreDataMember]
        public List<RunRequestCompletedEventHandler> Callbacks { get; private set; }
        [IgnoreDataMember]
        public List<RunRequestProgressChangedEventHandler> ProgressCallbacks { get; private set; }

        [IgnoreDataMember]
        public object UserState { get; set; }

        [OnDeserializing]
        public void OnDeserializing(StreamingContext context)
        {
            Callbacks = new List<RunRequestCompletedEventHandler>();
            ProgressCallbacks = new List<RunRequestProgressChangedEventHandler>();
            UserState = null;
        }
    }

    public class DownloadIsoFileRequest : DataRequest<IsolatedStorageFileStream>
    {
        public DownloadIsoFileRequest(Uri uri, string isoFilePath)
        {
            AllowReadStreamBuffering = false;
            IsoFilePath = isoFilePath;
            Uri = uri;
        }

        public DownloadIsoFileRequest(Uri uri, string isoFilePath, long bytesReceived, long totalBytesToReceive)
            : this(uri, isoFilePath)
        {
            BytesReceived = PreviousBytesReceived = bytesReceived;
            TotalBytesToReceive = totalBytesToReceive;
        }

        public override void RunRequestAsync()
        {
            if (PreviousBytesReceived == TotalBytesToReceive && TotalBytesToReceive != 0)
            {
                RequestStatus = MichMan.Utilities.Requests.RequestStatus.Succeeded;
                return;
            }
            Debug.WriteLine(String.Format("Downloading file \"{0}\".", Uri.ToString()));
            if (PreviousBytesReceived != TotalBytesToReceive && TotalBytesToReceive != 0)
            {
                Debug.WriteLine(String.Format("\tAlready have {0} of {1} bytes.", PreviousBytesReceived, TotalBytesToReceive));
            }

            base.RunRequestAsync();
        }

        private long PreviousBytesReceived { get; set; }
        public long BytesReceived { get; private set; }
        public long TotalBytesToReceive { get; private set; }
        public string IsoFilePath { get; private set; }

        protected override HttpWebRequest NewWebRequest()
        {
            HttpWebRequest request = base.NewWebRequest();
            request.AllowReadStreamBuffering = AllowReadStreamBuffering;
            if (BytesReceived != 0 && TotalBytesToReceive > BytesReceived)
            {
                request.Headers["Range"] = "bytes=" + BytesReceived.ToString(NumberFormatInfo.InvariantInfo) + "-" + TotalBytesToReceive.ToString(NumberFormatInfo.InvariantInfo);
            }
            return request;
        }

        protected IsolatedStorageFileStream PartialResponse { get; set; }
        protected override void ProcessPartialResult(byte[] buffer, int length, int total, int expected)
        {
            if (TotalBytesToReceive != 0 && expected + PreviousBytesReceived != TotalBytesToReceive)
            {
                // The file has changed.  
                Error = new InvalidOperationException("File has changed.");
                return;
            }

            TotalBytesToReceive = expected;

            if (PartialResponse == null)
            {
                try
                {
                    IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication();
                    if (!isoFile.DirectoryExists(Path.GetDirectoryName(IsoFilePath)))
                    {
                        isoFile.CreateDirectory(Path.GetDirectoryName(IsoFilePath));
                    }

                    PartialResponse = isoFile.OpenFile(IsoFilePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
                }
                catch (Exception e)
                {
                    Error = e;
                }
            }

            try
            {
                PartialResponse.Write(buffer, 0, length);
                PartialResponse.Flush();
                BytesReceived = total;
            }
            finally
            {
                if (total == expected)
                {
                    if (PartialResponse != null)
                    {
                        PartialResponse.Seek(0, SeekOrigin.Begin);
                        PartialResponse.Close();
                        Response = PartialResponse;
                    }
                }
            }
        }
    }

}
